#include "storage.h"
#include <fstream>

#include <boost/filesystem.hpp>
#include "net/core.h"
#include "net/libgo.h"
#include "json11/json11.hpp"
#include "libzqutil/log.h"

using Json = json11::Json;
namespace libzq
{
	namespace http
	{
		const static std::string session_path = "/tmp/zq-crow/";
		void FileSessionStorage::save(const string & key, const unordered_map<string, string>& data)
		{
			string session_file_name = session_path + key + ".session";
			std::ofstream session_file_stream;
			session_file_stream.open(session_file_name);
			for (const auto& d : data)
			{
				session_file_stream << d.first << "=" << d.second << "\n";
			}
			session_file_stream.close();
		}

		unordered_map<string, string> FileSessionStorage::load(const string & key)
		{
			if (boost::filesystem::is_directory(session_path) == false) boost::filesystem::create_directory(session_path);

			string session_file_name = session_path + key + ".session";
			std::ifstream session_file_stream(session_file_name);

			unordered_map<string, string> session_data;
			string line;
			while (getline(session_file_stream, line))
			{
				int pos = line.find("=");
				if (pos == string::npos) continue;
				string key = line.substr(0, pos);
				string val = line.substr(pos + 1, string::npos);
				session_data[key] = val;
			}
			return std::move(session_data);
		}

		void FileSessionStorage::remove(const string& key)
		{
			if (boost::filesystem::is_directory(session_path) == false) return;
			string session_file_name = session_path + key + ".session";
			boost::filesystem::remove(session_file_name);
		}

		static std::string status_string(cpp_redis::client::connect_state status)
		{
			if (status == cpp_redis::client::connect_state::dropped)
				return "DROPPED";
			else if (status == cpp_redis::client::connect_state::start)
				return "START";
			else if (status == cpp_redis::client::connect_state::sleeping)
				return "SLEEPING";
			else if (status == cpp_redis::client::connect_state::ok)
				return "OK";
			else if (status == cpp_redis::client::connect_state::failed)
				return "FAILED";
			else if (status == cpp_redis::client::connect_state::lookup_failed)
				return "LOOKUP FAILED";
			else if (status == cpp_redis::client::connect_state::stopped)
				return "STOPED";
			else return "UNKNOWN";
		}

		cpp_redis::client RedisSessionStorage::_client;
		bool RedisSessionStorage::_storage_has_inited = false;
		void RedisSessionStorage::init(const char * host, unsigned short port, unsigned short timeout_msecs/* = 300*/, unsigned short max_reconnects/* = 3*/, unsigned short reconnect_interval_msecs/* = 30*/)
		{
			if (_storage_has_inited)
			{
				LOGW("SessionStorage::init duplicated invoking");
			}
			RedisSessionStorage::_storage_has_inited = true;
			RedisSessionStorage::_client.connect(host, port, [](const std::string& host, std::size_t port, cpp_redis::client::connect_state status)
			{
				if (status == cpp_redis::client::connect_state::ok)
				{
					LOGI("SessionStorage connect to redis server [%s:%d] success", host, port);
				}
				else if (status == cpp_redis::client::connect_state::dropped)
				{
					LOGE("SessionStorage connect to redis server [%s:%d] failed", host, port);
				}
				else
				{
					LOGT("SessionStorage connect to redis server [%s:%d] status [%d]", host, port, status_string(status));
				}
			}, timeout_msecs, max_reconnects, reconnect_interval_msecs);
			CORE.install_stop([]()
			{
				RedisSessionStorage::_client.disconnect();
			});
		}

		bool RedisSessionStorage::check()
		{
			if (RedisSessionStorage::_client.is_connected())
			{
				return true;
			}
			else if (!RedisSessionStorage::_storage_has_inited)
			{
				LOGE("RedisSessionStore::init should be invoked first");
				return false;
			}
			else
			{
				LOGE("SessionStorage's connection to redis server dropped");
				return false;
			}
		}

		void RedisSessionStorage::save(const string & key, const unordered_map<string, string>& data)
		{
			if (RedisSessionStorage::check())
			{
				Json::object obj;
				for (const auto& d : data)
				{
					obj[d.first] = d.second;
				}
				string session_key = key + ".session";
				string session_data = Json(obj).dump();
				RedisSessionStorage::_client.set(session_key, session_data);
				RedisSessionStorage::_client.sync_commit();
			}
		}

		unordered_map<string, string> RedisSessionStorage::load(const string & key)
		{
			if (RedisSessionStorage::check())
			{
				string session_key = key + ".session";
				co_chan<string> session_data_chan(1);
				RedisSessionStorage::_client.get(session_key, [session_data_chan](cpp_redis::reply& reply)
				{
					session_data_chan << (reply.is_string() ? reply.as_string() : "");
				});
				RedisSessionStorage::_client.commit();
				string session_data = "";
				session_data_chan >> session_data;
				string err;
				auto session_json = json11::Json::parse(session_data.c_str(), err);
				unordered_map<string, string> session_map;
				for (auto& obj : session_json.object_items())
				{
					session_map[obj.first] = obj.second.string_value();
				}
				return std::move(session_map);
			}
			static unordered_map<string, string> empty;
			return empty;
		}

		void RedisSessionStorage::remove(const string& key)
		{
			if (RedisSessionStorage::check())
			{
				string session_key = key + ".session";
				std::vector<string> key_vec;
				key_vec.emplace_back(session_key);
				RedisSessionStorage::_client.del(key_vec);
			}
		}

	}
}